package com.fkjslee;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

import com.fkjslee.Task.FieldValueAndType;

import static com.fkjslee.Task.CHECK_TIME;

/**
 * @author fkjslee
 * @date 2018/1/29 at 15:11
 * Copyright (c) 2018 fkjslee. All rights reserved.
 */

@RestController
public class DCOPController {

    private static final Logger log = LoggerFactory.getLogger(DCOPController.class);
    private JSONObject allParam = null;
    private static Vector<Task> allQueue = new Vector<>();
    private Vector<Task> queue;

    @Autowired
    private RestTemplate restTemplate;

    /**
     * @param type      describe what kind of agent you want include: agentsName fieldsName newValue <br>
     * @param agentName describe what kind of agent you use, it must be a name given by DCOP. (if type is agentsName this parameter is not need.) <br>
     * @param fieldName describe what kind of field you use, it must be a name given by DCOP. (only type is newValue, this parameter is need.) <br>
     * @author fkjslee <br>
     * @Description A REST interface to provide all parameter message <br>
     */
    @RequestMapping(value = "/getParam")
    public String getParam(HttpServletResponse response, String type, String agentName, String fieldName) {
        log.info("client get parameter message from server, type = {}", type);
        response.addHeader("Access-Control-Allow-Origin", "*");
        if (allParam == null) {
            HttpThread httpThread = new HttpThread(Tool.PARAM_PROVIDER + "/getParam", "");
            new Thread(httpThread).start();
            allParam = JSONObject.fromObject(httpThread.getResult());
        }
        Integer id = 0;
        JSONArray jsonArray = new JSONArray();
        String result = null;
        if (type.equals("agentsName")) {
            Set<String> setAgent = allParam.keySet();
            //noinspection Duplicates
            for (String agent : setAgent) {
                if (allParam.getJSONObject(agent).keySet().size() == 0) //必须要有参数的才显示
                    continue;
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("text", agent);
                jsonObject.put("id", String.valueOf(id++));
                jsonArray.add(jsonObject);
            }
            result = jsonArray.toString();
        } else if (type.equals("fieldsName")) {
            JSONObject jsonObjectField = allParam.getJSONObject(agentName);
            Set<String> setField = jsonObjectField.keySet();
            //noinspection Duplicates
            for (String field : setField) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("text", field);
                jsonObject.put("id", String.valueOf(id++));
                jsonArray.add(jsonObject);
            }
            result = jsonArray.toString();
        } else if (type.equals("newValue")) {
            JSONObject jsonObjectField = allParam.getJSONObject(agentName);
            JSONObject jsonObjectAnnotation = jsonObjectField.getJSONObject(fieldName);
            String paramType = (String) jsonObjectAnnotation.keySet().toArray()[0];
            if (paramType.equals("DiscreteParam")) {
                JSONObject discreteParam = jsonObjectAnnotation.getJSONObject("DiscreteParam");
                JSONArray acceptedValues = discreteParam.getJSONArray("acceptedValues");
                String defaultValue = (String) discreteParam.get("defaultValue");
                JSONArray newAccept = new JSONArray();
                for (Object acceptedValue : acceptedValues) {
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("text", acceptedValue);
                    jsonObject.put("id", String.valueOf(id++));
                    newAccept.add(jsonObject);
                }
                JSONObject newDiscreteParam = new JSONObject();
                newDiscreteParam.put("acceptedValues", newAccept);
                newDiscreteParam.put("defaultValue", defaultValue);
                JSONObject all = new JSONObject();
                all.put("DiscreteParam", newDiscreteParam);
                result = all.toString();
            } else result = jsonObjectAnnotation.toString();

        }
        log.info("client get parameter from server, result = {}", result);
        return result;
    }

    @RequestMapping(value = "/assignTask")
    public String assignTask(HttpServletResponse response, String paramGroup, String userName, MultipartFile zipFile, String validateTime) {
        // zip file is needed
        if (zipFile == null) {
            return "{\"type\":\"fail\", \"code\": \"4\", \"reason\", \"需要一个zip文件\"}";
        }
        log.info("server receive paramGroup and assign task, userName = {}, zipFile = {}, paramGroup={}, ", userName, zipFile.getOriginalFilename(), paramGroup);
        response.addHeader("Access-Control-Allow-Origin", "*");
        //if the zip file is really end with "zip"?
        String zipName = zipFile.getOriginalFilename();
        if (zipName.lastIndexOf(".") == -1 || !"zip".equals(zipName.substring(zipName.lastIndexOf(".") + 1))) {
            return "{\"type\":\"fail\", \"code\": \"5\", \"reason\", \"上传文件的类型必须是zip\"}";
        }

        //store zip file
        String storeZipName = null;
        try {
            storeZipName = Tool.uploadFile(zipFile.getBytes(), "zipFileBuffer/", zipName.substring(0, zipName.lastIndexOf(".")), "zip");
            storeZipName = storeZipName.substring(storeZipName.lastIndexOf("/") + 1);
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.info("server store zip file, file name = {}", storeZipName);

        //get remain task size
        JSONObject jObjRemainTaskSize = JSONObject.fromObject(HttpThread.setParamAndRun(Tool.LOCAL_HOST_AND_PORT + "/getRemainTaskSize", "userName=" + userName, true, " get max task size "));
        if (!jObjRemainTaskSize.getString("code").equals("0"))
            return "{\"type\":\"error\", \"code\", \"6\", \"reason\":\"" + jObjRemainTaskSize.getString("reason") + "\"}";
        Integer remainTaskSize = Integer.valueOf(jObjRemainTaskSize.getString("number"));

        //get need time
        JSONObject jObjNeedTime = JSONObject.fromObject(HttpThread.setParamAndRun(Tool.LOCAL_HOST_AND_PORT + "/getNeedTime", "userName=" + userName, true, " DCOPController.java get need time"));
        if (!jObjRemainTaskSize.getString("code").equals("0"))
            return "{\"type\":\"error\", \"code\", \"6\", \"reason\":\"" + jObjRemainTaskSize.getString("reason") + "\"}";
        String needTime = jObjNeedTime.getString("needTime");

        //change param group to Cartesian product
        queue = new Vector<>();
        JSONArray jArrayParam = JSONArray.fromObject(paramGroup);
        for (int i = 0; i < jArrayParam.size(); ++i) {
            JSONObject jObjParam = jArrayParam.getJSONObject(i);
            String agentName = jObjParam.getString("thAgentName");
            String fieldType = jObjParam.getString("thFieldType");
            String fieldName = jObjParam.getString("thFieldName");
            String fieldValue = jObjParam.getString("thFieldValue");
            Boolean findAgent = false;
            for (Task t : queue) {
                if (t.agentName.equals(agentName)) {
                    findAgent = true;
                    break;
                }
            }
            if (fieldType.equals("hyper")) {
                if (!findAgent) {
                    Task task = new Task(userName, -1, validateTime, storeZipName, Long.valueOf(needTime));
                    if (task.fields.containsKey(fieldName))
                        return assignTaskResult(1, "出现了重复值,对于: " + agentName + "." + fieldName + " 请检查!");
                    task.fields.put(fieldName, new FieldValueAndType(fieldValue, "hyper"));
                    task.agentName = agentName;
                    queue.add(task);
                } else {
                    Vector<Task> tempQueue = new Vector<>();
                    for (Task t : queue) {
                        if (t.agentName.equals(agentName)) {
                            if (t.fields.containsKey(fieldName))
                                return assignTaskResult(1, "出现了重复值,对于: " + agentName + "." + fieldName + " 请检查!");
                            t.fields.put(fieldName, new FieldValueAndType(fieldValue, "hyper"));
                            tempQueue.add(t);
                        } else {
                            tempQueue.add(t);
                        }
                    }
                    queue = tempQueue;
                }
            }
            if (fieldType.equals("discrete")) {
                String[] newValues = fieldValue.split(",");
                if (!findAgent) {
                    if (newValues.length > remainTaskSize)
                        return "{\"type\":\"error\", \"code\":\"7\", \"reason\":\"超过任务数量限制\"}";
                    for (String value : newValues) {
                        Task task = new Task(userName, -1, validateTime, storeZipName, Long.valueOf(needTime));
                        if (task.fields.containsKey(fieldName))
                            return assignTaskResult(1, "出现了重复值,对于: " + agentName + "." + fieldName + " 请检查!");
                        task.agentName = agentName;
                        task.fields.put(fieldName, new FieldValueAndType(value, "discrete"));
                        queue.add(task);
                    }
                } else {
                    if (newValues.length * queue.size() > remainTaskSize)
                        return "{\"type\":\"error\", \"code\":\"7\", \"reason\":\"超过任务数量限制\"}";
                    Vector<Task> tempQueue = new Vector<>();
                    for (Task t : queue) {
                        if (t.agentName.equals(agentName)) {
                            for (String newValue : newValues) {
                                Task addTask = new Task(userName, newValue.equals(newValues[0]) ? t.getTaskId() : -1, validateTime, storeZipName, Long.valueOf(needTime));
                                addTask.agentName = t.agentName;
                                Map<String, FieldValueAndType> newFields = new HashMap<>();
                                for (String key : t.fields.keySet()) {
                                    FieldValueAndType value = t.fields.get(key);
                                    newFields.put(key, value);
                                }
                                if (newFields.containsKey(fieldName))
                                    return assignTaskResult(1, "出现了重复值,对于: " + agentName + "." + fieldName + " 请检查!");
                                newFields.put(fieldName, new FieldValueAndType(newValue, "discrete"));
                                addTask.fields = newFields;
                                tempQueue.add(addTask);
                            }
                        } else {
                            tempQueue.add(t);
                        }
                    }
                    queue = tempQueue;
                }
            }
            if (fieldType.equals("continuous")) {
                JSONObject jObjConNewValues = JSONObject.fromObject("{" + fieldValue + "}");
                Double U = jObjConNewValues.getDouble("U");
                Double L = jObjConNewValues.getDouble("L");
                Double S = jObjConNewValues.getDouble("S");
                if (U < L)
                    return "{\"type\":\"error\", \"code\":\"8\", \"reason\":\"L的值应该小于U的值, 对于:" + agentName + "." + fieldName + "\"}";
                Integer length;
                if (((U - L) % S) < 1e-9)
                    length = Double.valueOf((U - L) / S + 1.1).intValue();
                else
                    length = Double.valueOf((U - L) / S + 1.0).intValue();
                if (!findAgent) {
                    if (length > remainTaskSize)
                        return "{\"type\":\"error\", \"code\":\"7\", \"reason\":\"超过任务数量限制\"}";
                    for (Double O = L; O <= U; O = O + S) {
                        Task addTask = new Task(userName, -1, validateTime, storeZipName, Long.valueOf(needTime));
                        if (addTask.fields.containsKey(fieldName))
                            return assignTaskResult(1, "出现了重复值,对于: " + agentName + "." + fieldName + " 请检查!");
                        addTask.agentName = agentName;
                        addTask.fields.put(fieldName, new FieldValueAndType(O.toString(), "continuous"));
                        queue.add(addTask);
                    }
                } else {
                    if (length * queue.size() > remainTaskSize)
                        return "{\"type\":\"error\", \"code\":\"7\", \"reason\":\"超过任务数量限制\"}";
                    Vector<Task> tempQueue = new Vector<>();
                    for (Task t : queue) {
                        if (t.agentName.equals(agentName)) {
                            for (Double O = L; O <= U; O = O + S) {
                                Task addTask = new Task(userName, O.equals(L) ? t.getTaskId() : -1, validateTime, storeZipName, Long.valueOf(needTime));
                                addTask.agentName = t.agentName;
                                Map<String, FieldValueAndType> newFields = new HashMap<>();
                                for (String key : t.fields.keySet()) {
                                    FieldValueAndType value = t.fields.get(key);
                                    newFields.put(key, value);
                                }
                                if (addTask.fields.containsKey(fieldName))
                                    return assignTaskResult(1, "出现了重复值,对于: " + agentName + "." + fieldName + " 请检查!");
                                newFields.put(fieldName, new FieldValueAndType(O.toString(), "continuous"));
                                addTask.fields = newFields;
                                tempQueue.add(addTask);
                            }
                        } else {
                            tempQueue.add(t);
                        }
                    }
                    queue = tempQueue;
                }
            }
        }

        for (Task t : queue) {
            Set<Pair<String, String>> fieldsAndTypes = new HashSet<>();
            for (String key : t.fields.keySet()) {
                FieldValueAndType valueAndType = t.fields.get(key);
                fieldsAndTypes.add(new Pair<>(key, valueAndType.type));
            }

            // check if only have hyper
            Boolean onlyHyper = true;
            for (Pair<String, String> pair : fieldsAndTypes) {
                if (!pair.getValue().equals("hyper")) {
                    onlyHyper = false;
                    break;
                }
            }
            if (onlyHyper)
                return assignTaskResult(2, "仅仅包含了超参数(hyper),无意义,对于: " + t.agentName);

            //check are all hyper parameters set ?
            if (allParam == null) {
                allParam = JSONObject.fromObject(HttpThread.setParamAndRun(Tool.PARAM_PROVIDER + "/getParam", "", true, "server get param"));
            }
            // get all hyper which agent is t.agentName
            Set<String> allHyper = new HashSet<>();
            JSONObject jObjFields = allParam.getJSONObject(t.agentName);
            Set<String> setParamFields = jObjFields.keySet();
            for (String field : setParamFields) {
                JSONObject jObjAnnotation = jObjFields.getJSONObject(field);
                String paramType = (String) jObjAnnotation.keySet().toArray()[0];
                if (paramType.equals("HyperParam")) {
                    allHyper.add(field);
                }
            }
            // remove hyper field which appear at task
            for (String key : t.fields.keySet()) {
                if (t.fields.get(key).type.equals("hyper"))
                    allHyper.remove(key);
            }
            if (allHyper.size() != 0)
                return assignTaskResult(3, "请设置所超参数(hyper), 对于: " + t.agentName);
        }

//        if (0 == 0) {
//            return assignTaskResult(0, null);
//        }
//        send task to station
        allQueue.addAll(queue);
        if (Task.restTemplate == null)
            Task.restTemplate = restTemplate;
        String result = taskSchedule();
        if (!JSONObject.fromObject(result).getString("code").equals("0")) {
            return result;
        }
        return assignTaskResult(0, null);
    }

    private String assignTaskResult(Integer type, String errorMsg) {
        if (type == 0) {
            return "{\"type\":\"succeed\", \"code\":\"0\"}";
        } else {
            return "{\"type\":\"error\", \"code\":\"" + type.toString() + "\", \"reason\": \"" + errorMsg + "\"}";
        }
    }

    @RequestMapping(value = "taskComplete")
    public String taskComplete(Integer taskId) {
        for (Task t : allQueue) {
            if (t.getTaskId().equals(taskId)) {
                t.taskFinished();
                allQueue.remove(t);
                return "{\"type\":\"succeed\", \"code\":\"0\"}";
            }
        }
        taskSchedule();
        return "{\"type\":\"error\", \"code\":\"1\", \"reason\": \"任务队列中无此ID" + taskId.toString() + "\"}";
    }

    private synchronized static String taskSchedule() {
        for (Task t : allQueue) {
            String result = t.findOneStation();
            if (!JSONObject.fromObject(result).getString("code").equals("0")) {
                return result;
            }
        }
        return "{\"type\":\"success\", \"code\":\"0\"}";
    }

    @RequestMapping(value = "testForAssignTask")
    public String test(HttpServletRequest request) {
        String msg = "/testForAssignTask";
        return restTemplate.getForEntity(Tool.STATION_NAME + msg, String.class).getBody();
    }

    @RequestMapping(value = "taskResponse")
    public void taskResponse(Integer taskId) {
//        log.info("receive task response taskId = {}", taskId);
        for (Task t : allQueue) {
            if (t.getTaskId().equals(taskId)) {
                t.setLastTimeResponse(System.currentTimeMillis());
            }
        }
    }

    public static void queueCheckTimer() {
        new Thread(() -> {
            while (true) {
                for (Task t : allQueue) {
                    if (t.getFinished())
                        break;
                    if (!t.getHasSend())
                        continue;
                    if (t.checkDead()) {
                        allQueue.remove(t);
                        taskSchedule();
                    }
                    // if not send or too long to receive response from station, resend task
                }
                try {
                    Thread.sleep(CHECK_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private static class Pair<K, V> {
        private K key;
        private V value;
        public K getKey() {
            return key;
        }
        public V getValue() {
            return value;
        }

        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }

    public static void main(String[] args) {
        Double x = 8.0;
        Double y = 3.2;
        System.out.println(x % y);
        System.out.println(x / y);
    }
}
